package com.opages.mvc.adapter.consul.serviceregistry;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.opages.mvc.adapter.common.constant.AdapterConstant;
import com.opages.mvc.adapter.common.utils.ServerUtils;
import com.opages.mvc.adapter.consul.model.ConsulDiscoveryProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.ecwid.consul.v1.agent.model.NewService;
import com.opages.mvc.adapter.common.client.serviceregistry.Registration;
import org.springframework.web.context.ServletContextAware;

import javax.management.MalformedObjectNameException;
import javax.servlet.ServletContext;

/**
 * Consul注册<名词语义>信息
 * @author daizhong.liu
 *
 */
public class ConsulRegistration implements Registration {

	private static final String SEPARATOR1 = ":";
	public static final char SEPARATOR2 = '-';
	private final NewService service;
    private ConsulDiscoveryProperties properties;
    private String contextPath = "";

    public ConsulRegistration(ConsulDiscoveryProperties properties,ApplicationContext context,String contextPath) {
    	if(!StringUtils.isEmpty(contextPath)) {
    		this.contextPath = contextPath;
		}
    	this.service = new NewService();
		service.setId(getInstanceId(properties, context));
		if (!properties.isPreferAgentAddress()) {
			service.setAddress(properties.getHostname());
		}
		service.setPort(properties.getPort());
		service.setName(normalizeForDns(properties.getServiceName()));
		service.setTags(createTags(properties,this.contextPath));
		service.setEnableTagOverride(properties.getEnableTagOverride());
		service.setMeta(getMetadata(properties));
		NewService.Check check = new NewService.Check();
		check.setDeregisterCriticalServiceAfter(properties.getDeregisterCriticalServiceAfter());
		check.setTtl(properties.getTtl());
		service.setCheck(check);
        this.properties = properties;
    }

    public NewService getService() {
        return service;
    }

    public String getInstanceId() {
        return getService().getId();
    }
    @Override
    public String getServiceId() {
        return getService().getName();
    }
    @Override
    public String getHost() {
        return getService().getAddress();
    }
	@Override
	public boolean isSecure() {
		return this.properties.getScheme().equalsIgnoreCase("https");
	}

	@Override
	public String getScheme() {
		return this.properties.getScheme();
	}

	@Override
    public int getPort() {
        return getService().getPort();
    }

    @Override
    public Map<String, String> getMetadata() {
        if (properties.isTagsAsMetadata()) {
            return getMetadata(getService().getTags());
        }
        return getService().getMeta();
    }

    public Map<String, String> getMetadata(List<String> tags) {
        LinkedHashMap<String, String> metadata = new LinkedHashMap<>();
        if (tags != null) {
            for (String tag : tags) {
                String[] parts = StringUtils.delimitedListToStringArray(tag, "=");
                switch (parts.length) {
                    case 0:
                        break;
                    case 1:
                        metadata.put(parts[0], parts[0]);
                        break;
                    case 2:
                        metadata.put(parts[0], parts[1]);
                        break;
                    default:
                        String[] end = Arrays.copyOfRange(parts, 1, parts.length);
                        metadata.put(parts[0], StringUtils.arrayToDelimitedString(end, "="));
                        break;
                }

            }
        }

        return metadata;
    }

	public String getInstanceId(ConsulDiscoveryProperties properties,
			ApplicationContext context) {
		if (!StringUtils.hasText(properties.getInstanceId())) {
			return normalizeForDns(getDefaultInstanceId(context.getEnvironment(),properties.isIncludeHostnameInInstanceId()));
		}
		return normalizeForDns(properties.getInstanceId());
	}
	public String getDefaultInstanceId(PropertyResolver resolver,
			boolean includeHostname) {
		String hostname = null;
		if (includeHostname) {
			hostname = resolver.getProperty("spring.mvc.consul.discovery.hostname");
		}
		String appName = resolver.getProperty("spring.mvc.consul.discovery.serviceName");
		if(StringUtils.isEmpty(appName)) {
			if(!StringUtils.isEmpty(contextPath)) {
				appName = contextPath.replace("/","");
			}
		}
		String namePart = combineParts(hostname, SEPARATOR1, appName);
		String port = "";
		try {
			port = ServerUtils.getPort();
		} catch (MalformedObjectNameException e) {
			e.printStackTrace();
		}
		String indexPart = resolver.getProperty("spring.mvc.consul.discovery.instanceId",port);

		return combineParts(namePart, SEPARATOR1, indexPart);
	}
	public String combineParts(String firstPart, String separator,
			String secondPart) {
		String combined = null;
		if (firstPart != null && secondPart != null) {
			combined = firstPart + separator + secondPart;
		}
		else if (firstPart != null) {
			combined = firstPart;
		}
		else if (secondPart != null) {
			combined = secondPart;
		}
		return combined;
	}
	public String normalizeForDns(String s) {
    	if(StringUtils.isEmpty(s)) {
    		s = contextPath.replaceAll("/","");
		}
		if (s == null || !Character.isLetter(s.charAt(0))
				|| !Character.isLetterOrDigit(s.charAt(s.length() - 1))) {
			throw new IllegalArgumentException(
					"Consul service ids must not be empty, must start "
							+ "with a letter, end with a letter or digit, "
							+ "and have as interior characters only letters, "
							+ "digits, and hyphen: " + s);
		}

		StringBuilder normalized = new StringBuilder();
		Character prev = null;
		for (char curr : s.toCharArray()) {
			Character toAppend = null;
			if (Character.isLetterOrDigit(curr)) {
				toAppend = curr;
			}
			else if (prev == null || !(prev == SEPARATOR2)) {
				toAppend = SEPARATOR2;
			}
			if (toAppend != null) {
				normalized.append(toAppend);
				prev = toAppend;
			}
		}

		return normalized.toString();
	}
	public List<String> createTags(ConsulDiscoveryProperties properties,String contextPath) {
		List<String> tags = new LinkedList<>(properties.getTags());
		if (properties.isTagsAsMetadata()) {
			if (!StringUtils.isEmpty(properties.getInstanceZone())) {
				tags.add(properties.getDefaultZoneMetadataName() + "="
						+ properties.getInstanceZone());
			}
			if (!StringUtils.isEmpty(properties.getInstanceGroup())) {
				tags.add("group=" + properties.getInstanceGroup());
			}

			tags.add("secure="+ Boolean.toString(properties.getScheme().equalsIgnoreCase("https")));
		}
		if(!"".equals(contextPath)) {
			tags.add(AdapterConstant.APPLICATION_CONTEXT_PATH+"="+contextPath);
		}
		return tags;
	}
	private Map<String, String> getMetadata(ConsulDiscoveryProperties properties) {
		LinkedHashMap<String, String> metadata = new LinkedHashMap<>();
		if (!CollectionUtils.isEmpty(properties.getMetadata())) {
			metadata.putAll(properties.getMetadata());
		}

		if (!properties.isTagsAsMetadata()) {
			// add metadata from other properties. See createTags above.
			if (!StringUtils.isEmpty(properties.getInstanceZone())) {
				metadata.put(properties.getDefaultZoneMetadataName(),
						properties.getInstanceZone());
			}
			if (!StringUtils.isEmpty(properties.getInstanceGroup())) {
				metadata.put("group", properties.getInstanceGroup());
			}

			// store the secure flag in the tags so that clients will be able to figure
			// out whether to use http or https automatically
			metadata.put("secure",
					Boolean.toString(properties.getScheme().equalsIgnoreCase("https")));
		}

		return metadata;
	}
}
